fix(core): keep an expired NAT mapping expired until the app sends - #51
Merged
Conversation
Step 3 of the pipeline read and wrote the flow activity stamp in one touch(), so the write landed BEFORE the expiry verdict and applied to the drop path too: the packet rejected with reason "nat" stamped its own flow as active and brought the mapping straight back. Measured (timeout 5 s, one outbound at t=0, inbound only after): t=10 DROP, t=11/12/13 pass, t=20 DROP - one lost packet per timeout instead of a direction that stays shut. This impairment exists to test whether an application sends its keep-alives, and in that shape the test could not fail. - core.py: split into get() + a set() placed after the verdict, so the drop path skips the write. Same cost (touch was a get plus this same write): measured 160 ns/op both ways, difference below the noise floor. - core.py: drop _FlowTable.touch(), which had exactly one caller. - core.py: the comment now bounds the blackhole with measurements instead of a general claim - this flow alone still blackholed at t=200, one other flow driving _prune reopens it at t=30 (first rotation). - tests: test_a_dropped_packet_does_not_revive_an_expired_nat_mapping, verified by mutation (old order -> drops=[True, False, False, False]). - both changelogs (convention 39). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Found during an engineering audit of the engine (finding F1).
BeanCore.decide()step 3 read and wrote the flow activity stamp in a single_FlowTable.touch(), so the write happened before the expiry verdict and applied to the droppath too. The packet rejected with reason
nattherefore stamped its own flow as active andbrought the mapping straight back.
Measured before the fix (timeout 5 s, one outbound packet at
t=0, inbound only afterwards):So the direction lost roughly one packet per timeout and otherwise kept working, with nothing
outbound involved. This impairment exists to answer "does the application notice its mapping is
gone and send something to re-open it" - and in that shape the test could not fail: an app with no
keep-alive passed it. After the fix every inbound packet stays dropped until an outbound packet
re-opens the mapping, which is what a real NAT does.
How
core.py: split intoget()plus aset()placed after the verdict, so the drop path skipsthe write. Same cost -
touch()was agetplus this same write. Measured 160 ns/op both waysat 200k iterations, difference below the noise floor, so the packet hot path does not regress.
core.py:_FlowTable.touch()had exactly one caller and is removed with it.core.py: the comment now bounds the blackhole with measurements instead of a general claim.The drop path returns above the
_prune()call, so it never rotates anything itself: with thisflow alone the blackhole was still holding at
t=200; with one other flow driving_pruneitreopened at
t=30, the first rotation. That is the flow table's documented safe direction (itcan lose an impairment, never invent one) and is a separate matter from the resurrection fixed
here.
For the reviewer
order turns it red with
drops=[True, False, False, False]- the exact symptom above.test_nat_expiry,test_nat_outbound_refreshes).Neither of them pinned the buggy behaviour, which is why it survived; that gap is what the new
test closes.
nat_timeoutis left at its default of0(off).Checklist
python -m pytest testspasses locally (670 tests, 0 failures, elevated shell).python smoke_gui.pypasses.tests/test_core.py::test_a_dropped_packet_does_not_revive_an_expired_nat_mapping).lang/en.jsonandlang/pl.jsonupdated - not applicable, no UI strings in this change.CHANGELOG.md; technical ones and new tests inCHANGELOG-INTERNAL.md, under[Unreleased].type(scope): summary).VERSION.txt.🤖 Generated with Claude Code